﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SharedGenomics.Core.Collections
{
    public class TimedBuffer<T>
    {
        private readonly TimeSpan _signalTimeout;
        private readonly Action _callback;
        private readonly ManualResetEvent _waitHandle;
        private bool _isRunning;
        private readonly Queue<T> _buffer;
        private DateTime _lastSignalTime;

        private object _bufferLock = new object();

        public TimedBuffer(TimeSpan signalTimeout, Action callback)
        {
            callback.ThrowIfNull("callback");

            this._signalTimeout = signalTimeout;
            this._callback = callback;
            this._buffer = new Queue<T>();
            this._lastSignalTime = TimeService.Now();
            this._waitHandle = new ManualResetEvent(false);

            this._isRunning = true;
            Thread monitorThread = new Thread(this.MonitorQueue) { IsBackground = true };
            monitorThread.Start();
        }

        public void Add(T item)
        {
            lock (this._bufferLock)
            {
                this._buffer.Enqueue(item);
                this._waitHandle.Set();
            }
        }

        public IEnumerable<T> UnBuffer(int count)
        {
            if (count > 0)
            {
                T[] dequeued = null;
                lock (this._bufferLock)
                {
                    dequeued = this._buffer.Dequeue(count).ToArray();
                    if (this._buffer.Count == 0)
                    {
                        this._waitHandle.Reset();
                    }
                }

                return dequeued;
            }
            else
            {
                return new T[] { };
            }
        }

        protected virtual bool ShouldNotify()
        {
            //the buffer should signal if:
            // - the timeout since the last notification has expired
            return (TimeService.Now() - this._lastSignalTime) > this._signalTimeout;
        }

        protected int BufferCount
        {
            get
            {
                lock (this._bufferLock)
                {
                    return this._buffer.Count;
                }
            }
        }

        private void MonitorQueue()
        {
            while (this._isRunning)
            {
                if (this._waitHandle.WaitOne(100))
                {
                    if (this.ShouldNotify())
                    {
                        this.NotifyBufferExpiry();
                    }
                }
            }
        }

        private void NotifyBufferExpiry()
        {
            //update last signal time
            this._lastSignalTime = TimeService.Now();
            try
            {
                //callback should not bring background thread down
                this._callback();
            }
            catch (Exception)
            {
            }
        }
    }
}
